package ar.uba.fi.tonyvaliente.tree;

import ar.uba.fi.tonyvaliente.files.File;
import ar.uba.fi.tonyvaliente.files.FileBlock;
import ar.uba.fi.tonyvaliente.files.FileException;
import ar.uba.fi.tonyvaliente.files.FileRecord;
import ar.uba.fi.tonyvaliente.files.Persistor;
import ar.uba.fi.tonyvaliente.files.PersistorException;
import ar.uba.fi.tonyvaliente.files.RecordPosition;

public class FileBPlusTreeNodeReference<K extends Comparable<K>, V> extends BPlusTreeNodeReference<K, V> {
	private FileBPlusTree<K, V> tree;
	private int block;
	private int record;
	private Persistor<BPlusTreeNode<K, V>> persistor;
	private BPlusTreeNode<K, V> node; 

	public FileBPlusTreeNodeReference(FileBPlusTree<K, V> tree, int block, int record) {
		this.setTree(tree);
		this.setBlock(block);
		this.setRecord(record);
		this.setPersistor(new FileBPlusTreeNodePersistor<K, V>(tree));
		try {
			this.setNode(null);
		} catch (BPlusTreeException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public BPlusTreeNode<K, V> getNode() throws BPlusTreeException {
		if (this.getBlock() != -1 && this.getRecord() != -1) {
			if (this.node == null) {
				try {
					FileBlock fileBlock = this.getTree().getFile().getBlock(this.getBlock());
					FileRecord fileRecord = fileBlock.getRecord(this.getRecord());
					this.setNode(this.getPersistor().deserialize(fileRecord.getData()));
				} catch (FileException e) {
					e.printStackTrace();
					// TODO: change exception message
					throw new BPlusTreeException("1", e);
				} catch (PersistorException e) {
					e.printStackTrace();
					// TODO: change exception message
					throw new BPlusTreeException("2", e);
				}
			}
		}
		return this.node;
	}

	@Override
	public void setNode(BPlusTreeNode<K, V> node) throws BPlusTreeException {
		this.node = node;
		if (this.node != null) {
			this.node.setThisReference(this);
			if (this.getBlock() == -1 && this.getRecord() == -1) {
				try {
					RecordPosition recordPosition = this.getTree().getFile().addRecord(new FileRecord(this.getPersistor().serialize(this.node)));
					this.setBlock(recordPosition.getBlockNumber());
					this.setRecord(recordPosition.getOffset());
				} catch (FileException e) {
					e.printStackTrace();
					// TODO: change exception message
					throw new BPlusTreeException("1", e);
				} catch (PersistorException e) {
					e.printStackTrace();
					// TODO: change exception message
					throw new BPlusTreeException("2", e);
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void updateNode() throws BPlusTreeException {
		if (this.getBlock() != -1 && this.getRecord() != -1) {
			if (this.node != null) {
				try {
					File file = this.getTree().getFile();
					FileBlock fileBlock = file.getBlock(this.getBlock());
					FileRecord fileRecord = fileBlock.getRecord(this.getRecord());
					fileRecord.setData(this.getPersistor().serialize(this.node));
					boolean recordUpdated = fileBlock.updateRecord(this.getRecord(), fileRecord);
					if (!recordUpdated) {
						int oldBlock = this.getBlock(); 
						int oldRecord = this.getRecord(); 
						fileBlock.eraseRecord(this.getRecord());
						file.updateBlock(oldBlock, fileBlock);
						RecordPosition position = file.addRecord(fileRecord);
						file.updateBlock(position.getBlockNumber(), fileBlock);
						
						this.setBlock(position.getBlockNumber());
						this.setRecord(position.getOffset());
						
						BPlusTreeNodeReference<K, V> parentReference = this.node.getParentReference();
						if(parentReference != null)
						{
							InnerBPlusTreeNode<K, V> parentNode = (InnerBPlusTreeNode<K, V>)parentReference.getNode();
							FileBPlusTreeNodeReference<K, V> parentToChildReference = null;
							for(BPlusTreeNodeReference<K, V> reference : parentNode.getReferences()) {
								FileBPlusTreeNodeReference<K, V> fileReference = (FileBPlusTreeNodeReference<K, V>)reference;
								if (fileReference.getBlock() == oldBlock && fileReference.getRecord() == oldRecord) {
									parentToChildReference = fileReference;
									break;
								}
							}
							if (parentToChildReference != null) {
								parentToChildReference.setBlock(this.getBlock());
								parentToChildReference.setRecord(this.getRecord());
								parentReference.updateNode();
							}
						}
						
						if (this.node.isLeaf()) {
							FileBPlusTreeNodeReference<K, V> leftReference = (FileBPlusTreeNodeReference<K, V>)((LeafBPlusTreeNode<K, V>)this.node).getPreviousReference();
							if (leftReference != null) {
								LeafBPlusTreeNode<K, V> leftNode = (LeafBPlusTreeNode<K, V>)leftReference.getNode();
								leftNode.setNextReference(this);
								leftReference.updateNode();
							}
							FileBPlusTreeNodeReference<K, V> rightReference = (FileBPlusTreeNodeReference<K, V>)((LeafBPlusTreeNode<K, V>)this.node).getNextReference();
							if (rightReference != null) {
								LeafBPlusTreeNode<K, V> rightNode = (LeafBPlusTreeNode<K, V>)rightReference.getNode();
								rightNode.setPreviousReference(this);
								rightReference.updateNode();
							}
						}
						
						if (this.node.isInner()) {
							InnerBPlusTreeNode<K, V> innerNode = (InnerBPlusTreeNode<K, V>)this.node;
							for(BPlusTreeNodeReference<K, V> reference : innerNode.getReferences()) {
								BPlusTreeNode<K, V> childNode = reference.getNode();
								childNode.setParentReference(this);
								reference.updateNode();
							}
						}
					} else {
						file.updateBlock(this.getBlock(), fileBlock);
					}
				} catch (FileException e) {
					e.printStackTrace();
					// TODO: change exception message
					throw new BPlusTreeException("1", e);
				} catch (PersistorException e) {
					e.printStackTrace();
					// TODO: change exception message
					throw new BPlusTreeException("2", e);
				}
			}
		}
	}

	private FileBPlusTree<K, V> getTree() {
		return this.tree;
	}

	private void setTree(FileBPlusTree<K, V> tree) {
		this.tree = tree;
	}

	protected int getBlock() {
		return this.block;
	}

	protected void setBlock(int block) {
		this.block = block;
	}

	protected int getRecord() {
		return this.record;
	}

	protected void setRecord(int record) {
		this.record = record;
	}

	private Persistor<BPlusTreeNode<K, V>> getPersistor() {
		return this.persistor;
	}

	private void setPersistor(Persistor<BPlusTreeNode<K, V>> persistor) {
		this.persistor = persistor;
	}
}
